package io.github.junxworks.qt.modules.mm.service.impl;

import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.shiro.SecurityUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import io.github.junxworks.ep.auth.model.UserModel;
import io.github.junxworks.ep.core.exception.BusinessException;
import io.github.junxworks.ep.qlexp.CheckResult;
import io.github.junxworks.ep.qlexp.RuleEngine;
import io.github.junxworks.ep.sys.constants.RecordStatus;
import io.github.junxworks.ep.sys.constants.YesNo;
import io.github.junxworks.junx.core.util.StringUtils;
import io.github.junxworks.qt.constants.QtConstants;
import io.github.junxworks.qt.modules.mm.dto.ExpAnalyzeResult;
import io.github.junxworks.qt.modules.mm.dto.MmExpressionCondition;
import io.github.junxworks.qt.modules.mm.dto.MmExpressionDto;
import io.github.junxworks.qt.modules.mm.entity.MmExpDsRela;
import io.github.junxworks.qt.modules.mm.entity.MmExpExpRela;
import io.github.junxworks.qt.modules.mm.entity.MmExpVarRela;
import io.github.junxworks.qt.modules.mm.entity.MmExpression;
import io.github.junxworks.qt.modules.mm.mapper.MmExpressionMapper;
import io.github.junxworks.qt.modules.mm.service.MmDatastreamService;
import io.github.junxworks.qt.modules.mm.service.MmExpressionService;
import io.github.junxworks.qt.modules.mm.service.MmVariableService;
import io.github.junxworks.qt.modules.mm.vo.MmDatastreamVo;
import io.github.junxworks.qt.modules.mm.vo.MmExpressionVo;
import io.github.junxworks.qt.modules.mm.vo.MmVariableVo;

@Service
public class MmExpressionServiceImpl implements MmExpressionService {

	@Autowired
	private RuleEngine engine;

	@Autowired
	private MmExpressionMapper mmExpressionMapper;

	@Autowired
	private MmVariableService variableService;

	@Autowired
	private MmDatastreamService datastreamService;

	@Override
	public List<MmExpressionVo> queryList(MmExpressionCondition condition) {
		return mmExpressionMapper.queryList(condition);
	}

	@Override
	public MmExpressionVo queryMmExpressionById(Long id) {
		return mmExpressionMapper.queryMmExpressionById(id);
	}

	@Override
	@Transactional
	public void save(MmExpressionDto dto) throws Exception {
		checkCanSave(dto);
		MmExpression entity = new MmExpression();
		BeanUtils.copyProperties(dto, entity);
		UserModel user = (UserModel) SecurityUtils.getSubject().getPrincipal();
		Long userId = user.getId();
		if (entity.getId() == null) {
			entity.setCreateTime(new Date());
			entity.setCreateUser(userId);
			entity.setStatus(RecordStatus.NORMAL.getValue());
			mmExpressionMapper.insertWithoutNull(entity);
		} else {
			entity.setUpdateTime(new Date());
			entity.setUpdateUser(userId);
			mmExpressionMapper.updateWithoutNull(entity);
			Long id = entity.getId();
			mmExpressionMapper.deleteExpDsRela(id);
			mmExpressionMapper.deleteExpVarRela(id);
			mmExpressionMapper.deleteExpExpRela(id);
		}
		if (entity.getStatus() == null || RecordStatus.DELETED.getValue() != entity.getStatus()) {
			Long expId = entity.getId();
			if (StringUtils.notNull(entity.getExpression())) {
				ExpAnalyzeResult ares = analyzeExp(entity.getExpression());
				//数据流解析
				mmExpressionMapper.insertBatch(ares.getDs().stream().flatMap(dsName -> {
					MmDatastreamVo ds = datastreamService.queryDataStreamByName(dsName);
					if (ds == null) {
						throw new WrongExpressionException("不是有效的数据流\"" + dsName + "\"");
					}
					if (YesNo.NO.getValue() == ds.getExpable()) {
						throw new WrongExpressionException("数据流\"" + dsName + "\"不能用于表达式");
					}
					MmExpDsRela edr = new MmExpDsRela();
					edr.setExpId(expId);
					edr.setDsId(ds.getId());
					return Stream.of(edr);
				}).collect(Collectors.toList()));
				//变量解析
				mmExpressionMapper.insertBatch(ares.getVariables().stream().flatMap(vName -> {
					MmVariableVo var = variableService.queryMmVariableByName(vName);
					if (var == null) {
						throw new WrongExpressionException("不是有效的指标\"" + vName + "\"");
					}
					MmExpVarRela edr = new MmExpVarRela();
					edr.setExpId(expId);
					edr.setVarId(var.getId());
					return Stream.of(edr);
				}).collect(Collectors.toList()));
				//表达式解析
				mmExpressionMapper.insertBatch(ares.getExps().stream().flatMap(expName -> {
					if (entity.getExpName().equals(expName)) {
						throw new WrongExpressionException("表达式不能引用自己");
					}
					MmExpressionVo exp = mmExpressionMapper.queryMmExpressionByName(expName);
					if (exp == null) {
						throw new WrongExpressionException("不是有效的表达式\"" + expName + "\"");
					}
					int count = mmExpressionMapper.countExpRela(exp.getId(), expId);
					if (count > 0) {
						throw new WrongExpressionException("表达式\"" + expName + "\"也引用了本表达式，不能循环引用");
					}
					MmExpExpRela eer = new MmExpExpRela();
					eer.setExpIdFrom(expId);
					eer.setExpIdTo(exp.getId());
					return Stream.of(eer);
				}).collect(Collectors.toList()));
				queryAllRelatedExpression(expId); //校验一下是否存在循环引用
			}
		}
	}

	private void checkCanSave(MmExpressionDto dto) {
		UserModel user = (UserModel) SecurityUtils.getSubject().getPrincipal();
		Long userId = user.getId();
		if (StringUtils.notNull(dto.getExpName())) {
			MmExpressionVo _m = mmExpressionMapper.queryMmExpressionByName(dto.getExpName());
			if (_m != null) {
				if (dto.getId() == null || !_m.getId().equals(dto.getId())) {
					throw new BusinessException("已经存在表达式" + _m.getId() + "[" + dto.getExpName() + "]");
				}
			}
		}
		if (dto.getId() != null) {
			MmExpression m = mmExpressionMapper.selectEntityByID(MmExpression.class, dto.getId());
			if (!userId.equals(m.getCreateUser())) {
				throw new BusinessException("不是创建者不能操作数据");
			}
		}
	}

	public ExpAnalyzeResult analyzeExp(String exp) throws Exception {
		final ExpAnalyzeResult res = new ExpAnalyzeResult();
		if (StringUtils.isNull(exp)) {
			throw new WrongExpressionException("表达式不能为空");
		}
		CheckResult cr = engine.checkExpression(exp);
		if (!cr.isPass()) {
			throw new WrongExpressionException(cr.getMessage());
		}
		QtConstants.INNER_FUNCTIONS.forEach(f -> {
			String pattern = f + "\\s*\\(.+?\\)";
			Pattern r = Pattern.compile(pattern);
			Matcher m = r.matcher(exp);
			while (m.find()) {
				String matched = m.group(0);
				matched = StringUtils.replace(matched, " ", "");
				String sub = matched.substring(matched.indexOf("(") + 2, matched.indexOf(")") - 1);
				res.addRela(f, sub);
			}
		});
		return res;
	}

	@Override
	public MmExpressionVo queryMmExpressionByName(String expName) {
		return mmExpressionMapper.queryMmExpressionByName(expName);
	}

	@Override
	public String checkExpCanDelete(Long id) {
		String res = "";
		String varRela = mmExpressionMapper.queryRelaExpNamesByExpId(id);
		if (StringUtils.notNull(varRela)) {
			res += "被表达式引用[" + varRela + "] ";
		}
		return res;
	}

	@Override
	public Set<Long> queryAllRelatedExpression(Long expId) {
		Set<Long> relaIds = Sets.newHashSet(mmExpressionMapper.queryRelaExpIdsByExpId(expId));
		if (relaIds != null && !relaIds.isEmpty()) {
			for (Long id : relaIds) {
				relaIds.addAll(queryRelatedExpression(id, expId));
			}
		}
		return relaIds;
	}

	private Set<Long> queryRelatedExpression(Long expId, Long rootExpId) {
		Set<Long> relaIds = Sets.newHashSet(mmExpressionMapper.queryRelaExpIdsByExpId(expId));
		if (relaIds != null && !relaIds.isEmpty()) {
			if (relaIds.contains(rootExpId)) {
				throw new WrongExpressionException("表达式循环引用[" + mmExpressionMapper.queryMmExpressionById(expId).getExpName() + "]");
			}
			for (Long id : relaIds) {
				relaIds.addAll(queryRelatedExpression(id, rootExpId));
			}
		}
		return relaIds;
	}

	@Override
	public List<Long> queryAllDatastreamByIds(List<Long> expIds) {
		Set<Long> res = Sets.newHashSet();
		res.addAll(mmExpressionMapper.queryAllDatastreamIdsByExpIds(extractAllExpressionIdsByExpIds(expIds)));
		List<Long> varIds = mmExpressionMapper.queryAllVariablesIdsByExpIds(extractAllExpressionIdsByExpIds(expIds));
		res.addAll(variableService.queryAllDatastreamByVarIds(varIds));
		return Lists.newArrayList(res);
	}

	@Override
	public List<MmVariableVo> queryAllVariableByIds(List<Long> expIds) {
		List<Long> varIds = mmExpressionMapper.queryAllVariablesIdsByExpIds(extractAllExpressionIdsByExpIds(expIds));
		return variableService.queryAllVariableByVarIds(varIds);
	}

	@Override
	public List<MmExpressionVo> queryAllExpressionsByIds(List<Long> expIds) {
		return mmExpressionMapper.queryAllExpressionsByIds(extractAllExpressionIdsByExpIds(expIds));
	}

	private List<Long> extractAllExpressionIdsByExpIds(List<Long> expIds) {
		Set<Long> _expIds = Sets.newHashSet();
		_expIds.addAll(expIds);
		expIds.stream().forEach(i -> {
			_expIds.addAll(queryAllRelatedExpression(i));
		});
		return Lists.newArrayList(_expIds);
	}
}
